﻿using System;

namespace Netbits.Common.General
{
	/// <summary>
	/// Represents a simple implementation to handle byte-buffer
	/// </summary>
	public class ByteBuffer
	{
		#region Private Variables

		/// <summary>
		/// The number of bytes stored in the buffer.
		/// This is not necessarily the actual length of
		/// the internal byte array.  It is the number of
		/// bytes as seen by the user.
		/// </summary>
		int _Count;
		/// <summary>
		/// The internal byte array managed by the class.
		/// </summary>
		byte[] _Buffer;

		#endregion

		#region Public Constance

		/// <summary>
		/// The default capacity of the buffer (in bytes) if an
		/// initial capacity is not specified by the developer.
		/// </summary>
		public const int DefaultCapacity = 256;

		#endregion

		#region Constructor

		/// <summary>
		/// Creates a buffer with the default capacity.
		/// </summary>
		/// <seealso cref="DefaultCapacity"/>
		public ByteBuffer()
		{
			this._Count = 0;
			this._Buffer = new byte[DefaultCapacity];
		}

		/// <summary>
		/// Creates a buffer with the specified capacity.
		/// </summary>
		/// <param name="Capacity_">The initial capacity of the buffer</param>
		public ByteBuffer( int Capacity_ )
		{
			if( Capacity_ < 1 )
				throw new ArgumentOutOfRangeException( "Capacity_" );

			this._Count = 0;
			this._Buffer = new byte[Capacity_];
		}

		/// <summary>
		/// Creates a buffer and initializes it with an existing byte array.
		/// </summary>
		/// <param name="Data_">The data to copy to the buffer.</param>
		public ByteBuffer( byte[] Data_ )
		{
			if( Data_ == null )
				throw new ArgumentNullException( "Data_" );

			this._Buffer = new byte[Data_.Length];
			Data_.CopyTo( _Buffer, 0 );
			this._Count = Data_.Length;
		}

		/// <summary>
		/// Initializes the byte buffer with a subset of bytes from a byte array.
		/// </summary>
		/// <param name="Data_">A byte array containing data to copy to the byte buffer.</param>
		/// <param name="Offset_">The offset of the source array at which to begin copying bytes.</param>
		/// <param name="Count_">The number of bytes from the source array to copy to the buffer.</param>
		public ByteBuffer( byte[] Data_, int Offset_, int Count_ )
		{
			this._Buffer = new byte[Count_];
			Append( Data_, Offset_, Count_ );
		}

		#endregion

		#region Public Function

		/// <summary>
		/// Appends a byte to the end of the buffer.
		/// </summary>
		/// <param name="Value_">The byte to append.</param>
		public void Append( byte Value_ )
		{
			_EnsureCapacity( _Count + 1 );
			_Buffer[_Count] = Value_;
			_Count++;
		}

		/// <summary>
		/// Appends an array of bytes to the end of the buffer.
		/// </summary>
		/// <param name="Bytes_">A byte array containing the bytes to append.</param>
		public void Append( byte[] Bytes_ )
		{
			if( Bytes_ == null || Bytes_.Length == 0 )
				return;

			_EnsureCapacity( _Count + Bytes_.Length );

			Array.Copy( Bytes_, 0, _Buffer, _Count, Bytes_.Length );
			_Count += Bytes_.Length;
		}

		/// <summary>
		/// Appends a subset of a byte array to the byte buffer.
		/// </summary>
		/// <param name="SourceArray_">An array containing bytes to append to the buffer.</param>
		/// <param name="SourceIndex_">The index of the source array at which to begin copying bytes into the buffer.</param>
		/// <param name="Count_">The number of bytes from the source array to append into the byte buffer.</param>
		public void Append( byte[] SourceArray_, int SourceIndex_, int Count_ )
		{
			if( SourceArray_ == null || SourceArray_.Length == 0 || Count_ == 0 )
				return;

			_EnsureCapacity( _Count + Count_ );

			Array.Copy( SourceArray_, SourceIndex_, _Buffer, _Count, Count_ );
			_Count += Count_;
		}

		/// <summary>
		/// Clears the contents of the buffer.
		/// </summary>
		/// <remarks>The buffer is not necessarily resized (trimmed).  Clearing the buffer will not reduce the memory consumed by the buffer.</remarks>
		public void Clear()
		{
			if( _Count > 0 )
				_Count = 0;
		}

		/// <summary>
		/// Collapses doubled bytes (e.g. two 0xFF bytes are collapsed into a single 0xFF).
		/// </summary>
		/// <param name="Value_">The value to collapse.</param>
		/// <seealso cref="Double"/>
		public void CollapseDoubles( byte Value_ )
		{
			if( _Count < 2 )
				return;

			int index = 0;
			do
			{
				index = Array.IndexOf<byte>( _Buffer, Value_, index );
				if( (index == -1) || (index >= _Count - 1) )
					break;

				index++;
				if( _Buffer[index] == Value_ )
					RemoveAt( index );

			}
			while( index <= _Count );
		}

		/// <summary>
		/// Searches for the specified value.
		/// </summary>
		/// <param name="Value_">The value to check.</param>
		/// <returns>True if the buffer contains the specified byte.</returns>
		public bool Contains( byte Value_ )
		{
			if( _Count == 0 )
				return false;

			for( int i = 0; i < _Count; i++ )
			{
				if( _Buffer[i] == Value_ )
					return true;
			}

			return false;
		}

		/// <summary>
		/// Doubles each instance of a value (e.g. a single 0xFF becomes two 0xFF bytes).
		/// </summary>
		/// <param name="Value_">The value to double.</param>
		/// <seealso cref="CollapseDoubles"/>
		public void Double( byte Value_ )
		{
			int index = IndexOf( Value_ );
			while( index != -1 )
			{
				Insert( index, Value_ );
				index = IndexOf( Value_, index + 2 );
			}
		}

		/// <summary>
		/// Ensures the internal buffer can hold a minimum number of elements.
		/// </summary>
		/// <param name="Minimum_">The minimum capacity of the buffer after resizing.</param>
		void _EnsureCapacity( int Minimum_ )
		{
			if( _Buffer.Length < Minimum_ )
			{
				int newCapacity = _Buffer.Length == 0 ? 16 : _Buffer.Length * 2;
				if( newCapacity < Minimum_ )
					newCapacity = Minimum_;

				Capacity = newCapacity;
			}
		}

		/// <summary>
		/// Searches the buffer for a byte with a specified value.
		/// </summary>
		/// <param name="Value_">The value to locate in the buffer.</param>
		/// <returns>Returns the 0-based index of the first byte with the specified value, or -1 if no such byte has been stored in the buffer.</returns>
		public int IndexOf( byte Value_ )
		{
			if( _Count == 0 )
				return -1;

			return Array.IndexOf( _Buffer, Value_, 0, _Count );
		}

		/// <summary>
		/// Searches the buffer for a value located at or after a starting index.
		/// </summary>
		/// <param name="Value_">The value to locate in the buffer.</param>
		/// <param name="StartIndex_">The starting index to begin the search.</param>
		/// <returns>The zero-based index of the first value at or after the starting index, or -1 if the value could not be located.</returns>
		public int IndexOf( byte Value_, int StartIndex_ )
		{
			return Array.IndexOf( _Buffer, Value_, StartIndex_, _Count - StartIndex_ );
		}

		/// <summary>
		/// Inserts a byte into the buffer.
		/// </summary>
		/// <param name="Index_">The zero-based index in the buffer where the byte should be inserted.</param>
		/// <param name="Value_">The value to insert into the buffer.</param>
		public void Insert( int Index_, byte Value_ )
		{
			if( (Index_ < 0) || (Index_ > _Count) )
				throw new ArgumentOutOfRangeException( "index" );

			if( _Count == _Buffer.Length )
				_EnsureCapacity( _Count + 1 );

			if( Index_ < _Count )
				Array.Copy( _Buffer, Index_, _Buffer, Index_ + 1, _Count - Index_ );

			_Buffer[Index_] = Value_;
			_Count++;
		}

		/// <summary>
		/// Inserts a byte array into the specified
		/// location of the buffer.
		/// </summary>
		/// <param name="Index_">The location where inserting should begin.</param>
		/// <param name="Values_">A byte array of values to insert into the buffer.</param>
		public void Insert( int Index_, byte[] Values_ )
		{
			if( (Index_ < 0) || (Index_ > _Count) )
				throw new ArgumentOutOfRangeException( "index" );

			if( Values_.Length == 0 )
				return;

			_EnsureCapacity( _Count + Values_.Length );

			if( Index_ < _Count )
				Array.Copy( _Buffer, Index_, _Buffer, Index_ + Values_.Length, _Count - Index_ );

			Array.Copy( Values_, 0, _Buffer, Index_, Values_.Length );
			_Count += Values_.Length;
		}

		/// <summary>
		/// Removes the byte at the specified index of the buffer.
		/// </summary>
		/// <param name="Index_">The zero-based index of the byte to remove.</param>
		public void RemoveAt( int Index_ )
		{
			if( (Index_ < 0) || (Index_ >= _Count) )
				throw new ArgumentOutOfRangeException( "index" );

			if( Index_ == _Count - 1 )
			{
				_Count--;
				return;
			}

			if( Index_ < _Count )
				Array.Copy( _Buffer, Index_ + 1, _Buffer, Index_, _Count - Index_ - 1 );

			_Count--;
		}

		/// <summary>
		/// Replace the values.
		/// </summary>
		/// <param name="Value_"></param>
		/// <param name="NewValues_"></param>
		public void Replace( byte Value_, byte[] NewValues_ )
		{
			if( NewValues_ == null )
				throw new ArgumentNullException( "newValues" );

			int index = IndexOf( Value_ );

			while( index != -1 )
			{
				RemoveAt( index );
				Insert( index, NewValues_ );
				index = IndexOf( Value_, index + NewValues_.Length );
			}
		}

		/// <summary>
		/// Copies the contents of the buffer to a new array.
		/// </summary>
		/// <returns>A new byte array containing the contents of the buffer.</returns>
		public byte[] ToArray()
		{
			byte[] returnArray = new byte[_Count];
			Buffer.BlockCopy( _Buffer, 0, returnArray, 0, _Count );
			return returnArray;
		}

		#endregion

		#region Public Properties

		/// <summary>
		/// The capacity of the buffer.
		/// </summary>
		/// <remarks>
		/// The capacity reflects the number of bytes that can be stored in the buffer before a resize operation is necessary.
		/// The capacity is always a positive number equal to or greater than the <see cref="Count"/> of bytes stored in the buffer.
		/// </remarks>
		/// <seealso cref="Count"/>
		public int Capacity
		{
			get
			{
				return _Buffer.Length;
			}
			set
			{
				if( value < 1 || value == _Buffer.Length || value < _Count )
					return;

				byte[] newItems = new byte[value];
				Array.Copy( _Buffer, 0, newItems, 0, _Count );
				_Buffer = newItems;
			}
		}

		/// <summary>
		/// The number of bytes stored in the buffer.
		/// </summary>
		/// <seealso cref="Capacity"/>
		public int Count
		{
			get
			{
				return _Count;
			}
		}

		/// <summary>
		/// Gets or sets the byte at the specified index.
		/// </summary>
		public byte this[int Index_]
		{
			get
			{
				if( (Index_ < 0) || (Index_ >= _Count) )
					throw new ArgumentOutOfRangeException( "index" );

				return _Buffer[Index_];
			}
			set
			{
				if( (Index_ < 0) || (Index_ >= _Count) )
					throw new ArgumentOutOfRangeException( "index" );

				_Buffer[Index_] = value;
			}
		}

		#endregion

		#region Overrides

		/// <summary>
		/// Create a string with byte-buffer.
		/// </summary>
		/// <returns>string</returns>
		public override string ToString()
		{
			string s = string.Empty;
			for( int i = 0; i < _Count; i++ )
				s += "{0}:" + _Buffer[i];
			if( s.Length > 1 )
				s = s.Substring( s.Length - 1 );
			return s;
		}

		#endregion
	}
}
